package com.young.coderule.common.lock;

import com.young.coderule.common.redis.RedisSerializerUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @DESCRIPTION: 基于redis分布式锁
 * @USER: Young
 * @DATE: 2022/7/30 23:07
 */
public class RedisLock implements MyLock{

    private final long defautTimeoutMs = 5000L;
    private final long defautExpMs = 100000L;
    private static final RedisScript<Long> SCRIPT_DEL = new DefaultRedisScript("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class);
    private static final RedisScript<Object> SCRIPT_LOCK = new DefaultRedisScript("return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2])", Object.class);

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String LOCK_KEY_PREFIX = "LOCK:CODERULE:";

    /**
     * 加锁
     *
     * @param key        锁的key
     * @param identifier 锁标识
     * @param lockExp    锁的超时时间 毫秒
     * @return 锁标识 null表示加锁失败
     */
    public String lock(String key, String identifier, long lockExp) {
        return lock(key, identifier, lockExp, defautTimeoutMs);
    }

    /**
     * 加锁
     *
     * @param key        锁的key
     * @param identifier 锁标识
     * @param lockExp    锁的超时时间 毫秒
     * @param timeout    超时时间
     * @return 锁标识 null表示加锁失败
     */
    public String lock(String key, String identifier, long lockExp, long timeout) {
        identifier = getIdentifier(identifier);
        key = getKey(key);
        // 根据并发调整超时时间
        long end = System.currentTimeMillis() + timeout;
        while (System.currentTimeMillis() < end) {
            if (setNx(key, identifier, lockExp)) {
                // 返回value值，用于释放锁时间确认
                return identifier;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // 业务自行处理
                Thread.currentThread().interrupt();
            }
        }
        return null;
    }

    /**
     * 加锁 不阻塞
     * 用于分布式中只需要一个服务进行运算
     *
     * @param key        锁的key
     * @param identifier 锁标识
     * @param lockExp    锁的超时时间 毫秒
     * @return 锁标识 null表示加锁失败
     */
    public String lockNoWait(String key, String identifier, long lockExp) {
        identifier = getIdentifier(identifier);
        key = getKey(key);
        if (setNx(key, identifier, lockExp)) {
            // 返回value值，用于释放锁时间确认
            return identifier;
        }
        return null;
    }

    /**
     * 重置锁时效
     *
     * @param key        锁的key
     * @param identifier 锁标识
     * @param lockExp    锁的超时时间 毫秒
     * @return 锁标识 null表示失败
     */
    public String resetLockExp(String key, String identifier, long lockExp) {
        identifier = getIdentifier(identifier);
        String lockKey = getKey(key);

        if (identifier.equals(getValue(lockKey))) {
            expireByMill(lockKey, lockExp);
            // 返回value值，用于释放锁时间确认
            return identifier;
        }
        return null;
    }

    private String getIdentifier(String identifier) {
        if (StringUtils.isEmpty(identifier)) {
            LocalDateTime dateTime = LocalDateTime.now();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-DDThh:mm:ssZ");
            identifier = dateTimeFormatter.format(dateTime);
        }
        return identifier;
    }

    /**
     * 释放锁
     *
     * @param key        锁的key
     * @param identifier 释放锁的标识
     * @return
     */
    public boolean unlock(String key, String identifier) {
        key = getKey(key);
        Long execute = (Long) redisTemplate.execute(SCRIPT_DEL, Collections.singletonList(key), identifier);
//        return execute != null && execute >= 1L;
        return Objects.equals(execute, 1L);
    }

    public String getValue(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    public String getLockValue(String key) {
        return (String) redisTemplate.opsForValue().get(getKey(key));
    }

    public String getKey(String keyName) {
        return LOCK_KEY_PREFIX + keyName;
    }

    private String getLocalHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {

            return "";
        }
    }

    private void remove(String key) {
        redisTemplate.delete(key);
    }

    /**
     * @param key
     * @param value
     * @param ms
     * @return
     */
    private boolean setNx(final String key, final String value, long ms) {
        try {
            String execute = (String) redisTemplate.execute(SCRIPT_LOCK, RedisSerializerUtil.redisSerializerLong, RedisSerializerUtil.redisSerializerString, Collections.singletonList(key), value, ms);
            return "OK".equals(execute);
        } catch (Exception e) {
            return false;
        }
    }

    private void expireByMill(String key, long exp) {
        redisTemplate.expire(key, exp, TimeUnit.MILLISECONDS);
    }

    /**
     * @param key
     * @return 秒
     */
    public Long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    @Override
    public boolean lock(String key) {
        return null != this.lockNoWait(key, String.valueOf(Thread.currentThread().getId()), defautExpMs);
    }

    @Override
    public void unlock(String key) {
        this.unlock(key, String.valueOf(Thread.currentThread().getId()));
    }

    @Override
    public boolean isOwner(String key) {
        return String.valueOf(Thread.currentThread().getId()).equals(this.getLockValue(key));
    }
}
